function mxVertexHandler(state)
{
	if (state != null)
	{
		this.state = state;
		this.graph = state.view.graph;
		this.graph.addMouseListener(this);
		this.init();
		this.redraw();
	}
};
mxVertexHandler.prototype.graph = null;
mxVertexHandler.prototype.state = null;
mxVertexHandler.prototype.singleSizer = false;
mxVertexHandler.prototype.index = null;
mxVertexHandler.prototype.init = function()
{
	this.bounds = this.getSelectionBounds(this.state);
	this.selectionBorder = this.createSelectionShape(this.bounds);

	if (!this.graph.isHtmlLabel(this.state.cell) && (mxClient.IS_GC || mxClient.IS_SF))
	{

		this.selectionBorder.dialect = mxConstants.DIALECT_STRICTHTML;
		this.selectionBorder.init(this.graph.container);
	}
	else
	{

		this.selectionBorder.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG)
			? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
		this.selectionBorder.init(this.graph.getView().getOverlayPane());
	}

	if (this.selectionBorder.dialect == mxConstants.DIALECT_SVG)
	{
		this.selectionBorder.node.setAttribute('style', 'pointer-events:none;');
	}
	else
	{
		if (this.graph.isCellMovable(this.state.cell))
		{
			this.selectionBorder.node.style.cursor = 'move';
		}
		var isHtml = this.selectionBorder.dialect == mxConstants.DIALECT_STRICTHTML;
		mxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state.cell, null, true, isHtml, isHtml);
	}

	if (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells)
	{
		this.sizers = [];

		if (this.graph.isCellResizable(this.state.cell))
		{
			var i = 0;

			if (!this.singleSizer)
			{
				this.sizers.push(this.createSizer('nw-resize', i++));
				this.sizers.push(this.createSizer('n-resize', i++));
				this.sizers.push(this.createSizer('ne-resize', i++));
				this.sizers.push(this.createSizer('w-resize', i++));
				this.sizers.push(this.createSizer('e-resize', i++));
				this.sizers.push(this.createSizer('sw-resize', i++));
				this.sizers.push(this.createSizer('s-resize', i++));
			}
			this.sizers.push(this.createSizer('se-resize', i++));
			var geo = this.graph.model.getGeometry(this.state.cell);

			if (geo != null && !geo.relative
				&& !this.graph.isSwimlane(this.state.cell) && this.graph.isLabelMovable(this.state.cell))
			{
				this.sizers.push(this.createSizer('default', mxEvent.LABEL_HANDLE, (this.graph.dialect
					== mxConstants.DIALECT_SVG) ? 4 : 6, mxConstants.LABEL_HANDLE_FILLCOLOR));
			}
		}
		else if (this.graph.isCellMovable(this.state.cell)
			&& !this.graph.isCellResizable(this.state.cell) && this.state.width < 2 && this.state.height < 2)
		{
			this.sizers.push(this.createSizer('move', null, null, mxConstants.LABEL_HANDLE_FILLCOLOR));
		}
	}
};
mxVertexHandler.prototype.getSelectionBounds = function(state)
{
	return new mxRectangle(state.x, state.y, state.width, state.height);
};
mxVertexHandler.prototype.createSelectionShape = function(bounds)
{
	var shape = new mxRectangleShape(bounds, null, this.getSelectionColor());
	shape.strokewidth = this.getSelectionStrokeWidth();
	shape.isDashed = this.isSelectionDashed();
	return shape;
};
mxVertexHandler.prototype.getSelectionColor = function()
{
	return mxConstants.VERTEX_SELECTION_COLOR;
};
mxVertexHandler.prototype.getSelectionStrokeWidth = function()
{
	return mxConstants.VERTEX_SELECTION_STROKEWIDTH;
};
mxVertexHandler.prototype.isSelectionDashed = function()
{
	return mxConstants.VERTEX_SELECTION_DASHED;
};
mxVertexHandler.prototype.createSizer = function(cursor, index, size, fillColor)
{
	size = size || ((this.graph.dialect == mxConstants.DIALECT_SVG) ? 5 : 7);
	var bounds = new mxRectangle(0, 0, size, size);
	var sizer = this.createSizerShape(bounds, index, fillColor);

	if (this.graph.dialect == mxConstants.DIALECT_SVG)
	{
		sizer.dialect = mxConstants.DIALECT_PREFERHTML;
		sizer.init(this.graph.container);
	}
	else
	{
		sizer.dialect = this.graph.dialect;
		sizer.init(this.graph.getView().getOverlayPane());
	}
	var cell = this.state.cell;
	mxEvent.redirectMouseEvents(sizer.node, this.graph, cell, index, false, false, false, false);
	sizer.node.style.cursor = cursor;
	var self = this;
	mxEvent.addListener(sizer.node, 'dblclick', function(evt)
	{
		self.graph.dblClick(evt, self.state.cell);
		mxEvent.consume(evt);
	});

	if (!this.isSizerVisible(index))
	{
		sizer.node.style.visibility = 'hidden';
	}
	return sizer;
};
mxVertexHandler.prototype.isSizerVisible = function(index)
{
	return true;
};
mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor)
{
	return new mxRectangleShape(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
};
mxVertexHandler.prototype.moveSizerTo = function(shape, x, y)
{
	if (shape != null)
	{
		shape.bounds.x = x - shape.bounds.width / 2;
		shape.bounds.y = y - shape.bounds.height / 2;
		shape.redraw();
	}
};
mxVertexHandler.prototype.mouseDown = function(sender, me)
{
	if (!me.isConsumed() && this.graph.isEnabled()
		&& !this.graph.isForceMarqueeEvent(me.getEvent()) && this.state == me.getState() && me.getHandle() != null)
	{
		this.start(me.getX(), me.getY(), me.getHandle());
		me.consume();
	}
};
mxVertexHandler.prototype.start = function(x, y, index)
{
	var pt = mxUtils.convertPoint(this.graph.container, x, y);
	this.startX = pt.x;
	this.startY = pt.y;
	this.index = index;
};
mxVertexHandler.prototype.mouseMove = function(sender, me)
{
	if (!me.isConsumed() && this.index != null)
	{
		var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
		var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
		var scale = this.graph.getView().scale;

		if (this.index == mxEvent.LABEL_HANDLE)
		{
			if (gridEnabled)
			{
				point.x = this.graph.snap(point.x / scale) * scale;
				point.y = this.graph.snap(point.y / scale) * scale;
			}
			this.moveSizerTo(this.sizers[8], point.x, point.y);
			me.consume();
		}
		else if (this.index != null)
		{
			var dx = point.x - this.startX;
			var dy = point.y - this.startY;

			if (gridEnabled)
			{
				dx = this.graph.snap(dx / scale) * scale;
				dy = this.graph.snap(dy / scale) * scale;
			}
			this.bounds = this.union(this.state, dx, dy, this.index);
			this.drawPreview();
			me.consume();
		}
	}
};
mxVertexHandler.prototype.mouseUp = function(sender, me)
{
	if (!me.isConsumed() && this.index != null && this.state != null)
	{
		var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
		var scale = this.graph.getView().scale;
		var dx = (point.x - this.startX) / scale;
		var dy = (point.y - this.startY) / scale;

		if (this.graph.isGridEnabledEvent(me.getEvent()))
		{
			dx = this.graph.snap(dx);
			dy = this.graph.snap(dy);
		}
		this.resizeCell(this.state.cell, dx, dy, this.index);
		this.reset();
		me.consume();
	}
};
mxVertexHandler.prototype.reset = function()
{
	this.index = null;
	this.bounds = new mxRectangle(this.state.x, this.state.y, this.state.width, this.state.height);
	this.drawPreview();
};
mxVertexHandler.prototype.resizeCell = function(cell, dx, dy, index)
{
	var geo = this.graph.model.getGeometry(cell);

	if (index == mxEvent.LABEL_HANDLE)
	{
		geo = geo.clone();

		if (geo.offset == null)
		{
			geo.offset = new mxPoint(dx, dy);
		}
		else
		{
			geo.offset.x += dx;
			geo.offset.y += dy;
		}
		this.graph.model.setGeometry(cell, geo);
	}
	else
	{
		var bounds = this.union(geo, dx, dy, index);
		this.graph.resizeCell(cell, bounds);
	}
};
mxVertexHandler.prototype.union = function(bounds, dx, dy, index)
{
	if (this.singleSizer)
	{
		return new mxRectangle(bounds.x, bounds.y, Math.max(0, bounds.width + dx), Math.max(0, bounds.height + dy));
	}
	else
	{
		var left = bounds.x;
		var right = left + bounds.width;
		var top = bounds.y;
		var bottom = top + bounds.height;

		if (index > 4)
		{
			bottom = bottom + dy;
		}
		else if (index < 3)
		{
			top = top + dy;
		}

		if (index == 0 || index == 3 || index == 5)
		{
			left += dx;
		}
		else if (index == 2 || index == 4 || index == 7)
		{
			right += dx;
		}
		var width = right - left;
		var height = bottom - top;

		if (width < 0)
		{
			left += width;
			width = Math.abs(width);
		}

		if (height < 0)
		{
			top += height;
			height = Math.abs(height);
		}
		return new mxRectangle(left, top, width, height);
	}
};
mxVertexHandler.prototype.redraw = function()
{
	this.bounds = new mxRectangle(this.state.x, this.state.y, this.state.width, this.state.height);

	if (this.sizers != null)
	{
		var s = this.state;
		var r = s.x + s.width;
		var b = s.y + s.height;

		if (this.singleSizer)
		{
			this.moveSizerTo(this.sizers[0], r, b);
		}
		else
		{
			var cx = s.x + s.width / 2;
			var cy = s.y + s.height / 2;
			this.moveSizerTo(this.sizers[0], s.x, s.y);

			if (this.sizers.length > 1)
			{
				this.moveSizerTo(this.sizers[1], cx, s.y);
				this.moveSizerTo(this.sizers[2], r, s.y);
				this.moveSizerTo(this.sizers[3], s.x, cy);
				this.moveSizerTo(this.sizers[4], r, cy);
				this.moveSizerTo(this.sizers[5], s.x, b);
				this.moveSizerTo(this.sizers[6], cx, b);
				this.moveSizerTo(this.sizers[7], r, b);
				this.moveSizerTo(this.sizers[8], cx + s.absoluteOffset.x, cy + s.absoluteOffset.y);
			}
		}
	}
	this.drawPreview();
};
mxVertexHandler.prototype.drawPreview = function()
{
	this.selectionBorder.bounds = this.bounds;
	this.selectionBorder.redraw();
};
mxVertexHandler.prototype.destroy = function()
{
	this.graph.removeMouseListener(this);
	this.selectionBorder.destroy();
	this.selectionBorder = null;

	if (this.sizers != null)
	{
		for (var i = 0; i < this.sizers.length; i++)
		{
			this.sizers[i].destroy();
			this.sizers[i] = null;
		}
	}
};